"""
底背离分析器 - 检测价格与MACD的底背离信号
"""

import pandas as pd
import numpy as np
import matplotlib
matplotlib.use('Agg')  # 使用非交互式后端，不显示GUI窗口
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from datetime import datetime, timedelta
import logging
from typing import List, Tuple, Optional, Dict
from scipy.signal import argrelextrema
import warnings
warnings.filterwarnings('ignore')

# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class DivergenceAnalyzer:
    """底背离分析器类"""
    
    def __init__(self, 
                 fast_period: int = 12,
                 slow_period: int = 26, 
                 signal_period: int = 9,
                 min_distance: int = 5,
                 lookback_window: int = 10,
                 max_divergence_distance: int = 50):
        """
        初始化底背离分析器
        
        Args:
            fast_period: MACD快线周期，默认12
            slow_period: MACD慢线周期，默认26
            signal_period: MACD信号线周期，默认9
            min_distance: 局部极值点最小间距，默认5
            lookback_window: 寻找极值点的回望窗口，默认10
            max_divergence_distance: 底背离检测的最大距离，默认50
        """
        self.fast_period = fast_period
        self.slow_period = slow_period
        self.signal_period = signal_period
        self.min_distance = min_distance
        self.lookback_window = lookback_window
        self.max_divergence_distance = max_divergence_distance
    
    def calculate_macd(self, prices: pd.Series) -> pd.DataFrame:
        """
        计算MACD指标
        
        Args:
            prices: 价格序列（通常为收盘价）
            
        Returns:
            包含MACD指标的DataFrame
        """
        # 计算指数移动平均线
        ema_fast = prices.ewm(span=self.fast_period).mean()
        ema_slow = prices.ewm(span=self.slow_period).mean()
        
        # 计算MACD线
        macd_line = ema_fast - ema_slow
        
        # 计算信号线
        signal_line = macd_line.ewm(span=self.signal_period).mean()
        
        # 计算MACD柱状图
        macd_histogram = macd_line - signal_line
        
        macd_data = pd.DataFrame({
            'macd_line': macd_line,
            'signal_line': signal_line,
            'macd_histogram': macd_histogram
        }, index=prices.index)
        
        return macd_data
    
    def find_local_minima(self, data: pd.Series, min_distance: Optional[int] = None) -> List[int]:
        """
        寻找局部极小点
        
        Args:
            data: 数据序列
            min_distance: 最小间距，如果为None则使用实例变量
            
        Returns:
            局部极小点的索引列表
        """
        if min_distance is None:
            min_distance = self.min_distance
        
        # 使用scipy寻找局部极小值
        minima_indices = argrelextrema(
            data.values, 
            np.less, 
            order=self.lookback_window
        )[0]
        
        # 过滤掉距离太近的点
        filtered_minima = []
        if len(minima_indices) > 0:
            filtered_minima.append(minima_indices[0])
            
            for idx in minima_indices[1:]:
                if idx - filtered_minima[-1] >= min_distance:
                    filtered_minima.append(idx)
        
        return filtered_minima
    
    def detect_bullish_divergence(self, 
                                 price_data: pd.DataFrame,
                                 price_column: str = 'close') -> pd.DataFrame:
        """
        检测底背离信号
        
        Args:
            price_data: 价格数据DataFrame
            price_column: 用于分析的价格列名
            
        Returns:
            包含底背离信号的DataFrame
        """
        if price_column not in price_data.columns:
            raise ValueError(f"价格数据中没有找到列: {price_column}")
        
        prices = price_data[price_column]
        
        # 计算MACD
        macd_data = self.calculate_macd(prices)
        
        # 合并数据
        combined_data = pd.concat([price_data, macd_data], axis=1)
        
        # 寻找价格的局部极小点（新逻辑：只找价格极小点）
        price_minima_indices = self.find_local_minima(prices)
        
        logger.info(f"找到 {len(price_minima_indices)} 个价格局部极小点")
        
        # 检测底背离（新逻辑：基于价格极小点比较对应的MACD值）
        divergence_signals = self._identify_bullish_divergences_new(
            combined_data, 
            price_minima_indices, 
            price_column
        )
        
        # 添加MACD数据到原始数据
        result_data = combined_data.copy()
        result_data['price_minima'] = False
        result_data['macd_minima'] = False
        result_data['bullish_divergence'] = False
        
        # 标记局部极小点
        if price_minima_indices:
            result_data.iloc[price_minima_indices, result_data.columns.get_loc('price_minima')] = True
            # 新逻辑：价格极小点就是MACD对比点，所以也标记为macd_minima
            result_data.iloc[price_minima_indices, result_data.columns.get_loc('macd_minima')] = True
        
        # 标记底背离信号
        for signal in divergence_signals:
            idx = signal['current_index']
            result_data.iloc[idx, result_data.columns.get_loc('bullish_divergence')] = True
        
        return result_data, divergence_signals

    
    def _identify_bullish_divergences_new(self, 
                                        data: pd.DataFrame,
                                        price_minima: List[int],
                                        price_column: str) -> List[Dict]:
        """
        识别底背离信号（新逻辑）
        只基于K线收盘价的局部极小值点，比较对应的MACD DIF线值
        
        Args:
            data: 合并后的数据
            price_minima: 价格局部极小点索引
            price_column: 价格列名
            
        Returns:
            底背离信号列表
        """
        divergences = []
        
        # 需要至少两个价格极小点才能判断背离
        if len(price_minima) < 2:
            return divergences
        
        # 检查所有可能的价格极小点对
        for i in range(len(price_minima)):
            current_price_idx = price_minima[i]
            
            # 检查当前点之前的所有极小点
            for j in range(i):
                previous_price_idx = price_minima[j]
                
                # 检查距离是否在允许范围内
                distance = current_price_idx - previous_price_idx
                if distance > self.max_divergence_distance:
                    continue  # 距离太远，跳过
                
                current_price = data.iloc[current_price_idx][price_column]
                previous_price = data.iloc[previous_price_idx][price_column]
                
                # 底背离条件1: 当前价格低点 <= 前一个价格低点 (允许小幅上涨)
                price_tolerance = 0.02  # 允许2%的价格上涨容忍度
                if current_price <= previous_price * (1 + price_tolerance):
                    
                    # 新逻辑：直接获取价格极小点对应的MACD DIF线值
                    current_macd = data.iloc[current_price_idx]['macd_line']  # DIF线就是macd_line
                    previous_macd = data.iloc[previous_price_idx]['macd_line']
                    
                    # 底背离条件2: 当前MACD DIF > 前一个MACD DIF
                    # 底背离条件3: 局部极小值对应的MACD值必须为负数（更严格的条件）
                    if (current_macd > previous_macd and 
                        current_macd < 0 and previous_macd < 0):
                        
                        # 计算背离强度
                        price_change = (current_price - previous_price) / previous_price * 100
                        macd_change = (current_macd - previous_macd) / abs(previous_macd) * 100
                        divergence_strength = macd_change - price_change
                        
                        divergence = {
                            'current_index': current_price_idx,
                            'previous_index': previous_price_idx,
                            'current_price': current_price,
                            'previous_price': previous_price,
                            'current_macd': current_macd,
                            'previous_macd': previous_macd,
                            'current_macd_idx': current_price_idx,  # 现在MACD索引就是价格索引
                            'previous_macd_idx': previous_price_idx,  # 现在MACD索引就是价格索引
                            'price_change_pct': price_change,
                            'macd_change_pct': macd_change,
                            'divergence_strength': divergence_strength,
                            'timestamp': data.index[current_price_idx],
                            'distance': distance
                        }
                        
                        divergences.append(divergence)
                        
                        logger.info(f"检测到底背离: {data.index[current_price_idx]}")
                        logger.info(f"  价格变化: {price_change:.2f}%, MACD变化: {macd_change:.2f}%")
                        logger.info(f"  当前MACD: {current_macd:.6f}, 前一个MACD: {previous_macd:.6f}")
                        logger.info(f"  距离: {distance} 个数据点")
        
        return divergences
    
    def _find_nearest_macd_minimum(self, 
                                  price_idx: int, 
                                  macd_minima: List[int], 
                                  tolerance: int = 10) -> Optional[int]:
        """
        寻找距离价格极小点最近的MACD极小点
        
        Args:
            price_idx: 价格极小点索引
            macd_minima: MACD极小点索引列表
            tolerance: 容忍的最大距离
            
        Returns:
            最近的MACD极小点索引，如果没有找到则返回None
        """
        if not macd_minima:
            return None
        
        # 找到距离最近的MACD极小点
        distances = [abs(macd_idx - price_idx) for macd_idx in macd_minima]
        min_distance = min(distances)
        
        if min_distance <= tolerance:
            nearest_idx = distances.index(min_distance)
            return macd_minima[nearest_idx]
        
        return None
    
    def create_divergence_visualization(self, 
                                      data: pd.DataFrame,
                                      divergences: List[Dict],
                                      symbol: str,
                                      price_column: str = 'close',
                                      save_path: Optional[str] = None) -> None:
        """
        创建底背离可视化图表
        
        Args:
            data: 包含所有数据的DataFrame
            divergences: 底背离信号列表
            symbol: 交易对符号
            price_column: 价格列名
            save_path: 图表保存路径（可选）
        """
        fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(15, 12), sharex=True)
        
        # 第一个子图：价格走势
        ax1.plot(data.index, data[price_column], label='收盘价', color='blue', linewidth=1.5)
        
        # 标注价格局部极小点（K线局部极小值）
        price_minima_mask = data['price_minima']
        if price_minima_mask.any():
            price_minima_data = data[price_minima_mask]
            ax1.scatter(price_minima_data.index, price_minima_data[price_column], 
                       color='orange', s=80, marker='v', label='K线局部极小值', zorder=5,
                       edgecolors='black', linewidth=0.5)
            
            # 为每个局部极小值添加价格标注（从原数据读取并转为float，避免显示为0的问题）
            for idx in price_minima_data.index:
                price_value = float(data.loc[idx, price_column])
                ax1.annotate(f'{price_value:.2f}', 
                           xy=(idx, price_value), 
                           xytext=(0, -15), textcoords='offset points',
                           ha='center', va='top',
                           bbox=dict(boxstyle='round,pad=0.2', facecolor='orange', alpha=0.7),
                           fontsize=8)
        
        # 标注底背离信号
        if divergences:
            for div in divergences:
                current_idx = data.index[div['current_index']]
                previous_idx = data.index[div['previous_index']]
                
                # 标注当前点
                ax1.scatter(current_idx, div['current_price'], 
                           color='red', s=120, marker='^', zorder=6)
                
                # 连线显示背离
                ax1.plot([previous_idx, current_idx], 
                        [div['previous_price'], div['current_price']], 
                        color='red', linestyle='--', alpha=0.7, linewidth=2)
                
                # 添加文本标注
                ax1.annotate(f'底背离\n强度:{div["divergence_strength"]:.1f}', 
                           xy=(current_idx, div['current_price']), 
                           xytext=(10, 20), textcoords='offset points',
                           bbox=dict(boxstyle='round,pad=0.3', facecolor='yellow', alpha=0.8),
                           arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0'),
                           fontsize=9)
        
        ax1.set_title(f'{symbol} 价格走势与底背离信号', fontsize=16, fontweight='bold')
        ax1.set_ylabel('价格 (USDT)', fontsize=12)
        ax1.legend()
        ax1.grid(True, alpha=0.3)
        
        # 第二个子图：MACD线和信号线
        ax2.plot(data.index, data['macd_line'], label='MACD线', color='blue', linewidth=1.5)
        ax2.plot(data.index, data['signal_line'], label='信号线', color='red', linewidth=1.5)
        ax2.axhline(y=0, color='black', linestyle='-', alpha=0.3)
        
        # 标注MACD局部极小点
        macd_minima_mask = data['macd_minima']
        if macd_minima_mask.any():
            macd_minima_data = data[macd_minima_mask]
            ax2.scatter(macd_minima_data.index, macd_minima_data['macd_line'], 
                       color='orange', s=80, marker='v', label='MACD极小点', zorder=5)
        
        # 标注底背离对应的MACD点
        if divergences:
            for div in divergences:
                current_macd_idx = data.index[div['current_macd_idx']]
                previous_macd_idx = data.index[div['previous_macd_idx']]
                
                # 连线显示MACD背离
                ax2.plot([previous_macd_idx, current_macd_idx], 
                        [div['previous_macd'], div['current_macd']], 
                        color='green', linestyle='--', alpha=0.7, linewidth=2)
        
        ax2.set_title('MACD指标', fontsize=14)
        ax2.set_ylabel('MACD值', fontsize=12)
        ax2.legend()
        ax2.grid(True, alpha=0.3)
        
        # 第三个子图：MACD柱状图
        colors = ['green' if x >= 0 else 'red' for x in data['macd_histogram']]
        ax3.bar(data.index, data['macd_histogram'], color=colors, alpha=0.6, width=1)
        ax3.axhline(y=0, color='black', linestyle='-', alpha=0.5)
        
        ax3.set_title('MACD柱状图', fontsize=14)
        ax3.set_ylabel('MACD柱状图', fontsize=12)
        ax3.set_xlabel('时间', fontsize=12)
        ax3.grid(True, alpha=0.3)
        
        # 格式化x轴 - 详细的时间坐标
        time_span_hours = (data.index[-1] - data.index[0]).total_seconds() / 3600
        
        if time_span_hours <= 48:  # 2天内
            ax3.xaxis.set_major_locator(mdates.HourLocator(interval=4))
            ax3.xaxis.set_major_formatter(mdates.DateFormatter('%m-%d %H:%M'))
        elif time_span_hours <= 168:  # 7天内
            ax3.xaxis.set_major_locator(mdates.DayLocator(interval=1))
            ax3.xaxis.set_major_formatter(mdates.DateFormatter('%m-%d'))
        elif time_span_hours <= 720:  # 30天内
            ax3.xaxis.set_major_locator(mdates.DayLocator(interval=1))
            ax3.xaxis.set_major_formatter(mdates.DateFormatter('%m-%d'))
        else:  # 超过30天
            ax3.xaxis.set_major_locator(mdates.DayLocator(interval=3))
            ax3.xaxis.set_major_formatter(mdates.DateFormatter('%m-%d'))
        
        ax3.tick_params(axis='x', which='major', labelsize=10, rotation=45)
        
        plt.tight_layout()
        
        if save_path:
            plt.savefig(save_path, dpi=300, bbox_inches='tight')
            logger.info(f"底背离图表已保存到: {save_path}")
        
        plt.show()
    
    def generate_divergence_report(self, 
                                 divergences: List[Dict], 
                                 symbol: str, 
                                 period_days: int) -> str:
        """
        生成底背离分析报告
        
        Args:
            divergences: 底背离信号列表
            symbol: 交易对符号
            period_days: 分析周期天数
            
        Returns:
            报告字符串
        """
        report = f"""
==================== 底背离分析报告 ====================
交易对: {symbol}
分析周期: {period_days} 天
MACD参数: 快线{self.fast_period}, 慢线{self.slow_period}, 信号线{self.signal_period}
检测条件: 要求局部极小值对应的MACD值为负数（更严格的筛选）

================= 统计结果 =================
底背离信号总数: {len(divergences)}
"""
        
        if divergences:
            avg_strength = sum(div['divergence_strength'] for div in divergences) / len(divergences)
            report += f"平均背离强度: {avg_strength:.2f}\n"
            
            report += f"\n================= 信号详情 =================\n"
            
            for i, div in enumerate(divergences, 1):
                report += f"""
信号 {i}: {div['timestamp'].strftime('%Y-%m-%d %H:%M:%S')}
  价格变化: {div['previous_price']:.4f} → {div['current_price']:.4f} ({div['price_change_pct']:.2f}%)
  MACD变化: {div['previous_macd']:.6f} → {div['current_macd']:.6f} ({div['macd_change_pct']:.2f}%)
  背离强度: {div['divergence_strength']:.2f}
"""
        else:
            report += "在分析周期内未发现底背离信号。\n"
        
        report += "=" * 50
        
        return report
    
    def analyze_symbol_divergence(self, 
                                data: pd.DataFrame, 
                                symbol: str,
                                period_days: int = 30,
                                price_column: str = 'close',
                                save_chart: bool = True) -> Dict:
        """
        分析单个交易对的底背离情况
        
        Args:
            data: 价格数据DataFrame
            symbol: 交易对符号
            period_days: 分析周期天数
            price_column: 价格列名
            save_chart: 是否保存图表
            
        Returns:
            分析结果字典
        """
        logger.info(f"开始分析 {symbol} 的底背离情况...")
        
        # 检测底背离
        result_data, divergences = self.detect_bullish_divergence(data, price_column)
        
        # 生成报告
        report = self.generate_divergence_report(divergences, symbol, period_days)
        
        # 创建可视化
        if save_chart:
            save_path = f"{symbol}_divergence_analysis.png"
            self.create_divergence_visualization(result_data, divergences, symbol, price_column, save_path)
        
        # 打印报告
        print(report)
        
        return {
            'symbol': symbol,
            'divergences': divergences,
            'data_with_indicators': result_data,
            'report': report,
            'divergence_count': len(divergences)
        }
